/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2019年1月18日
 * V4.0
 */
package com.jphenix.kernel.classloader;

import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.share.lang.SString;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.exceptions.MsgException;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 全局包资源管理服务
 * 
 * 2019-11-04 修改了，扩展类路径中引用了其它扩展类路径时无效的错误
 * 2020-05-12 避免重复加载资源
 * 2020-05-24 增加避免重复加载机制纯属画蛇添足，导致无法加载扩展库
 *            之前在全局变量中增加了一个list，用来保存所有已经加载过的文件路径。但是遇到另外一个问题
 *            类A加载了扩展库，在当前类中的pUrlMap私有包路径中增加了对照关系。但是类B也加载了同样的
 *            扩展库，也需要增加对照关系，结果被避免重复的list拦截，认为已经加载过，然后就放弃加载了
 *            导致类B无法加载扩展库
 *            
 * @author MBG
 * 2019年1月18日
 */
@ClassInfo({"2020-05-24 21:57","全局包资源管理服务"})
@BeanInfo({"resourceservice"})
public class ResourceService extends ABase {

	//全局资源容器
	private final HashMap<String,ResourceEntry>             sourceMap = new HashMap<String,ResourceEntry>();
	//私有资源容器
	private final HashMap<String,Map<String,ResourceEntry>> pSourceMap = new HashMap<String,Map<String,ResourceEntry>>();
	//全局包路径序列
	private final List<String>                              urlList = new ArrayList<String>();
	//私有包路径序列
	private final HashMap<String,List<String>>              pUrlMap = new HashMap<String,List<String>>();
	
	/**
	 * 构造函数
	 * @author MBG
	 */
	public ResourceService() {
		super();
	}

	
	/**
	 * 获取指定资源
	 * @param sourceKey 资源主键
	 * @return          资源信息对象
	 * 2019年1月18日
	 * @author MBG
	 */
	public synchronized ResourceEntry getSource(String sourceKey) {
		if(sourceKey==null || sourceKey.length()<1) {
			return null;
		}
		if(sourceKey.startsWith("/")) {
			sourceKey = sourceKey.substring(1);
		}
		return sourceMap.get(sourceKey);
	}
	
	
	/**
	 * 获取指定资源（如果全局资源容器中没有，会从扩展私有包资源容器中获取）
	 * @param sourceKey 资源主键
	 * @param extLibs   扩展库相对路径数组
	 * @return
	 * 2019年1月18日
	 * @author MBG
	 */
	public synchronized ResourceEntry getSource(String sourceKey,List<String> extLibs) {
		if(sourceKey==null || sourceKey.length()<1) {
			return null;
		}
		if(sourceKey.startsWith("/")) {
			sourceKey = sourceKey.substring(1);
		}
		if(extLibs==null || extLibs.size()<1) {
			return sourceMap.get(sourceKey);
		}
		//先从全局资源中获取该值
		ResourceEntry res = sourceMap.get(sourceKey);
		if(res==null) {
			Map<String,ResourceEntry> pcSourceMap; //私有包资源信息容器
			for(String path:extLibs) {
				pcSourceMap = pSourceMap.get(path);
				if(pcSourceMap!=null) {
					res = pcSourceMap.get(sourceKey);
					if(res!=null) {
						return res;
					}
				}
			}
		}
		return null;
	}
	
	
	/**
	 * 获取指定资源（如果全局资源容器中没有，会从扩展私有包资源容器中获取）
	 * @param sourceKey 资源主键
	 * @param extLibs   扩展库相对路径数组
	 * @return
	 * 2019年1月18日
	 * @author MBG
	 */
	public synchronized ResourceEntry getSource(String sourceKey,String[] extLibs) {
		if(sourceKey==null || sourceKey.length()<1) {
			return null;
		}
		if(sourceKey.startsWith("/")) {
			sourceKey = sourceKey.substring(1);
		}
		if(extLibs==null || extLibs.length<1) {
			return sourceMap.get(sourceKey);
		}
		//先从全局资源中获取该值
		ResourceEntry res = sourceMap.get(sourceKey);
		if(res==null) {
			Map<String,ResourceEntry> pcSourceMap; //私有包资源信息容器
			for(int i=0;i<extLibs.length;i++) {
				pcSourceMap = pSourceMap.get(extLibs[i]);
				if(pcSourceMap!=null) {
					res = pcSourceMap.get(sourceKey);
					if(res!=null) {
						return res;
					}
				}
			}
		}
		return null;
	}
	
	/**
	 * 移除资源
	 * 注意：这里无需传入扩展包，因为没有什么场景需要从扩展包中移除资源
	 * 以后可能会增加使用超时后，自动移除扩展包资源，目前暂时不需要。在实际需要的时候再考虑这个功能
	 * @param sourceKey 资源主键
	 * 2019年1月18日
	 * @author MBG
	 */
	public void removeSource(String sourceKey) {
		if(sourceKey==null) {
			return;
		}
		if(sourceKey.startsWith("/")) {
			sourceMap.remove(sourceKey.substring(1));
		}else {
			sourceMap.remove(sourceKey);
		}
	}
	
	/**
	 * 返回全局包路径数组
	 * @return 全局包路径数组
	 * 2019年1月18日
	 * @author MBG
	 */
	public URL[] getURLs() {
		//构建返回值
		URL[] reUrl = new URL[urlList.size()];
		String path; //路径元素
		for(int i=0;i<urlList.size();i++) {
			path = urlList.get(i);
			try {
				reUrl[i] = new URL("file:/"+path);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		return reUrl;
	}
	
	
	/**
	 * 返回包含私有包路径（包含了全局包路径）的数组
	 * @param extLibs 私有包相对路径数组
	 * @return 包路径数组
	 * 2019年1月18日
	 * @author MBG
	 */
	public URL[] getURLs(List<String> extLibs) {
		//获取私有包路径信息主键
		List<String> pList = new ArrayList<String>(); //私有包序列
		if(extLibs!=null && extLibs.size()>0) {
			List<String> listEle; //路径信息元素
			for(String path:extLibs) {
				listEle = pUrlMap.get(path);
				if(listEle==null) {
					warning("-----------ResourceService: Not Find The ExtLib:["+path
							+"] In Path:[WEB-INF/ext_lib]");
				}else {
					pList.addAll(listEle);
				}
			}
		}
		//构建返回值
		URL[] reUrl = new URL[urlList.size()+pList.size()];
		String path; //路径元素
		for(int i=0;i<urlList.size();i++) {
			path = urlList.get(i);
			try {
				reUrl[i] = new URL("file:/"+path);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		if(pList!=null) {
			for(int i=0;i<pList.size();i++) {
				path = pList.get(i);
				try {
					reUrl[i] = new URL("file:/"+path);
				}catch(Exception e) {
					e.printStackTrace();
				}
			}
		}
		return reUrl;
	}
	
	
	/**
	 * 返回包含私有包路径（包含了全局包路径）的数组
	 * @param extLibs 私有包相对路径数组
	 * @return 包路径数组
	 * 2019年1月18日
	 * @author MBG
	 */
	public URL[] getURLs(String[] extLibs) {
		//获取私有包路径信息主键
		List<String> pList = new ArrayList<String>(); //私有包序列
		if(extLibs!=null && extLibs.length>0) {
			List<String> listEle; //路径信息元素
			for(int i=0;i<extLibs.length;i++) {
				listEle = pUrlMap.get(extLibs[i]);
				if(listEle==null) {
					warning("-----------ResourceService: Not Find The ExtLib:["+extLibs[i]
							+"] In Path:[WEB-INF/ext_lib]");
				}else {
					pList.addAll(listEle);
				}
			}
		}
		//构建返回值
		URL[] reUrl = new URL[urlList.size()+pList.size()];
		String path; //路径元素
		for(int i=0;i<urlList.size();i++) {
			path = urlList.get(i);
			try {
				reUrl[i] = new URL("file:/"+path);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		if(pList!=null) {
			for(int i=0;i<pList.size();i++) {
				path = pList.get(i);
				try {
					reUrl[i] = new URL("file:/"+path);
				}catch(Exception e) {
					e.printStackTrace();
				}
			}
		}
		return reUrl;
	}
	
	/**
	 * 增加压缩包中的资源信息
	 * 刘虻
	 * 2010-8-31 下午01:11:25
	 * @param zipFilePath 压缩包文件路径
	 */
	protected void addZipSource(String zipFilePath,String extLibPath) {
		//搜索结果路径序列
		ArrayList<String> pathList = new ArrayList<String>();
		//搜索压缩包
		SFilesUtil.searchZipFilePath(pathList,zipFilePath,null,null,false);
		if(pathList.size()>0) {
			if(extLibPath==null || extLibPath.length()<1) {
				if(urlList.contains(zipFilePath)) {
					return;
				}
				urlList.add(zipFilePath);
			}else {
				
				if(extLibPath.startsWith("/")) {
					extLibPath = extLibPath.substring(1);
				}
				
				List<String> pUrlList; //私有包路径序列
				if(pUrlMap.containsKey(extLibPath)) {
					pUrlList = pUrlMap.get(extLibPath);
					if(pUrlList==null) {
						pUrlList = new ArrayList<String>();
						pUrlMap.put(extLibPath,pUrlList);
					}
					if(pUrlList.contains(zipFilePath)) {
						return;
					}
					pUrlList.add(zipFilePath);
				}else {
					pUrlList = new ArrayList<String>();
					pUrlList.add(zipFilePath);
					pUrlMap.put(extLibPath,pUrlList);
				}
			}
			//构建压缩文件对象
			File zipFile = new File(zipFilePath);
			for(String path:pathList) {
				//压缩包中的文件元素
				path = BaseUtil.swapString(SString.valueOf(path),"\\","//");
				//构建资源信息类
				ResourceEntry re = new ResourceEntry();
				re.codeBase      = zipFilePath;
				re.inJar         = true;
				re.lastModified  = zipFile.lastModified();
				re.loadedClass   = null;
				re.source        = path;
				
				//保留开头的/ 所有资源第一个字符都是/
				re.sourceSubPath = path.substring(path.indexOf("!/")+1);
				//资源主键
				String key = re.sourceSubPath;
				if(key!=null && key.startsWith("/")) {
					key = key.substring(1);
				}
				if(extLibPath==null || extLibPath.length()<1) {
					synchronized(sourceMap) {
						//放入资源文件中
						sourceMap.put(key,re);
					}
				}else {
					synchronized(pSourceMap) {
						//获取对应的私有
						Map<String,ResourceEntry> pcSourceMap = pSourceMap.get(extLibPath);
						if(pcSourceMap==null) {
							pcSourceMap = new HashMap<String,ResourceEntry>();
							pSourceMap.put(extLibPath,pcSourceMap);
						}
						pcSourceMap.put(key,re);
					}
				}
			}
		}
	}
	
	/**
	 * 加入新的资源文件
	 * 用于增加一个资源文件
	 * 刘虻
	 * 2010-8-31 下午12:39:56
	 * @param basePath 根路径
	 * @param file 资源文件对象
	 */
	public void addSource(String basePath,File file,String extLibPath) {
		if(file.isDirectory() || !file.exists()) {
			//只处理文件
			return;
		}
		String filePath = file.getPath(); //获取文件全路径
		//整理路径格式
		basePath = BaseUtil.swapString(basePath,"\\","/");
		if(basePath.endsWith("/")) {
			//去掉末尾的路径符号
			basePath = basePath.substring(0,basePath.length()-1);
		}
		//资源路径
		String path = BaseUtil.swapString(filePath,"\\","/");
		String checkPath = path.toLowerCase();
		if(checkPath.endsWith("jar") || checkPath.endsWith("zip")) {
			addZipSource(path,extLibPath); //增加压缩包中的内容
			return;
		}
		if(extLibPath==null || extLibPath.length()<1) {
			if(urlList.contains(path)) {
				//该文件已存在
				return;
			}
			urlList.add(path);
		}else {
			
			if(extLibPath.startsWith("/")) {
				extLibPath = extLibPath.substring(1);
			}
			
			List<String> pUrlList; //对应的私有包路径序列
			if(pUrlMap.containsKey(extLibPath)) {
				pUrlList = pUrlMap.get(extLibPath);
				if(pUrlList==null) {
					pUrlList = new ArrayList<String>();
					pUrlMap.put(extLibPath,pUrlList);
				}
				if(pUrlList.contains(path)) {
					return;
				}
				pUrlList.add(path);
			}else {
				pUrlList = new ArrayList<String>();
				pUrlList.add(path);
				pUrlMap.put(extLibPath,pUrlList);
			}
		}
		try {
			//构建资源信息类
			ResourceEntry re = new ResourceEntry();
			re.codeBase = basePath;
			re.inJar = false;
			re.lastModified = file.lastModified();
			re.loadedClass = null;
			re.source = path;
			try {
				re.sourceSubPath = path.substring(basePath.length());
			}catch(Exception e) {
				throw new MsgException(this,"\nBasePath:"+basePath+"\nPath:"+path,e);
			}
			//资源主键
			String key = re.sourceSubPath;
			if(key!=null && key.startsWith("/")) {
				key = key.substring(1);
			}
			if(extLibPath==null || extLibPath.length()<1) {
				synchronized(sourceMap) {
					sourceMap.put(key,re);
				}
			}else {
				synchronized(pSourceMap) {
					//获取对应的私有
					Map<String,ResourceEntry> pcSourceMap = pSourceMap.get(extLibPath);
					if(pcSourceMap==null) {
						pcSourceMap = new HashMap<String,ResourceEntry>();
						pSourceMap.put(extLibPath,pcSourceMap);
					}
					pcSourceMap.put(key,re);
				}
			}
		}catch(Exception e) {
			e.printStackTrace();
			System.out.println("BasePath:"+basePath+"  File:"+file);
		}
	}
	
	
	/**
	 * 该方法用于增加一组用分号分割的路径或jar(zip)文件路径,该方法不能用于增加
	 * 一个非jar(zip)的资源文件，因为无法确定文件根路径和相对路径
	 * 加入新的路径
	 * @author 刘虻
	 * 2009-8-17上午01:33:05
	 * @param path        新的绝对路径（只能是单个路径，不能多个路径用分隔符分割）
	 * @param extSubPath  扩展路径（相对路径）
	 */
	public void addClassPath(String path,String extSubPath) {
		if(path==null || path.length()<1) {
			return;
		}
		//构建路径对象
		File file = new File(path);
		if(file.isDirectory()) {
			//库文件路径序列
			ArrayList<String> filePathList = new ArrayList<String>();
			filePathList.add(path); //将当前路径放入类路径序列中 比如：classes
			try {
				//搜索指定文件
				SFilesUtil.getFileList(filePathList,path,null,null,true,false);
				for(String ele:filePathList) {
					addSource(path,new File(ele),extSubPath);
				}
			}catch(Exception e) {
				e.printStackTrace();
			}
		}else {
			addSource(path,file,extSubPath);
		}
	}
}
